package glemipsc;

import glemipsc.implementations.Implementation;
import java.io.*;
import java.util.*;

public class GLEMipsC {
	public final class Environment {
		public String outputFile = "output.mips";
		public boolean preParseOnly = false;
		public boolean saveObjectFiles = false;
		public Implementation implementation = new glemipsc.implementations.glemips32.Implementation();
		public boolean asmFile = false;
		public boolean emulate = false;
	}
	
	public static GLEMipsC compiler;
	public Linker linker;

	public Environment environment;
	public ArrayList<String> includeList = new ArrayList<String>();
	private ArrayList<String> sources = new ArrayList<String>();
	private ArrayList<String> objects = new ArrayList<String>();
	private ObjectFile currentObjectFile = null;

	public GLEMipsC() {
		environment = new Environment();
	}
	
	public Environment getEnvironment() {
		return environment;
	}

	public void addSourceFile(String file) {
		if (!sources.contains(file))
			sources.add(file);
	}

	public void addObjectFile(String file) {
		if (!objects.contains(file))
			objects.add(file);
	}
	
	public void displayError(ErrorLevel level, String message) {
		System.err.println(level.name() + ": " + currentObjectFile.getSourceName() + ": " + message);
		if (level == ErrorLevel.ERROR)
			currentObjectFile.addError();
		else if (level == ErrorLevel.FATAL)
			System.exit(2);
	}

	public void compileFiles() {
		PipedOutputStream preParsedOutput = null;
		PipedInputStream preParsedSource = null;
		PipedInputStream in = null;
		Iterator<String> iter = sources.iterator();
		while (iter.hasNext()) {
			String currentFile = iter.next();

			PreParser scanner = null;
			try {
				scanner = new PreParser( new java.io.FileReader(currentFile) );
				if (!environment.preParseOnly) {
					preParsedOutput = new PipedOutputStream();
					preParsedSource = new PipedInputStream(preParsedOutput);
					scanner.setIncludePath(includeList);
					scanner.setOutputStream(preParsedOutput);
				}
				// Pre-parsing
				scanner.yylex();
				if (environment.preParseOnly)
					continue;
				Lexer lexer = new Lexer(preParsedSource);
				parser p = new parser(lexer);
				ObjectFile objFile = new ObjectFile(currentFile);
				currentObjectFile = objFile;
				objects.add(objFile.getObjectName());
				p.setObjectFile(objFile);
				p.parse();
				if (p.getErrorsNumber() + objFile.getErrors() > 0)
					System.err.println(currentFile + ": error: Compilation failed due to errors in file");
				else if (environment.saveObjectFiles)
					objFile.save();
				preParsedSource.close();
			}
			catch (java.io.FileNotFoundException e) {
				System.err.println("Error compiling "+currentFile+" : \""+e.getLocalizedMessage()+"\"");
				usage();
			}
			catch (java.io.IOException e) {
				System.err.println("IO error scanning file \""+currentFile+"\"");
				e.printStackTrace();
			}
			catch (Exception e) {
				System.err.println("Unexpected exception: ");
				e.printStackTrace();
			}
		}
	}

	public void linkFiles() {
		// Loading object files for linking
		Iterator<String> iter = objects.iterator();
		while (iter.hasNext()) {
			String name = iter.next();
			if (environment.saveObjectFiles || !ObjectFile.isLoaded(name)) {
				ObjectFile.load(name);
			}
		}
		// Create linker
		linker = new Linker(ObjectFile.getObjectFiles());
		try {
			FileOutputStream out = new FileOutputStream(this.environment.outputFile);
			linker.buildExecutableFile(out);
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] argv) {
		if (argv.length == 0) {
			usage();
		}
		else {
			compiler = new GLEMipsC();

			// Parsing command-line arguments
			for (int i = 0; i < argv.length; i++) {
				if (argv[i].startsWith("-")) {
					// This is a parameter
					if (argv[i].equals("-o")) {	// -o outputFile
						compiler.environment.outputFile = argv[++i];
					}
					else if (argv[i].equals("-p")) { // -p
						compiler.environment.preParseOnly = true;
					}
					else if (argv[i].equals("-s")) {
						compiler.environment.saveObjectFiles = true;
					}
					else if (argv[i].equals("-i32")) {
						if (!(compiler.environment.implementation instanceof glemipsc.implementations.glemips32.Implementation))
							compiler.environment.implementation = new glemipsc.implementations.glemips32.Implementation();
					}
					//else if (argv[i].equals("-i64")) {
					//	if (!(compiler.environment.implementation instanceof glemipsc.implementations.glemips64.Implementation))
					//		compiler.environment.implementation = new glemipsc.implementations.glemips64.Implementation();
					//}
					else if (argv[i].equals("-asm")) {
						compiler.environment.asmFile = true;
					}
					else if (argv[i].equals("-emu")) {
						compiler.environment.emulate = true;
				    /*Include Librerie*/
					}else if (argv[i].equals("-I")) {
							compiler.includeList.add(argv[++i]);
					}
				}
				else {
					// This is a file to parse
					if (argv[i].endsWith(".c"))
						compiler.addSourceFile(argv[i]);
					else if (argv[i].endsWith(".o"))
						compiler.addObjectFile(argv[i]);
				}
			}
			// Arguments parsing finished
			boolean filesPresent = false;
			if (!compiler.sources.isEmpty()) {
				compiler.compileFiles();
				filesPresent = true;
			}
			if (!compiler.objects.isEmpty()) {
				// Link object files
				compiler.linkFiles();
				filesPresent = true;
			}
			if (compiler.environment.emulate && compiler.linker != null) {
				GLEMipsEmu emu = new GLEMipsEmu();
				emu.setLinker(compiler.linker);
				emu.setEnv(compiler.environment);
				emu.emulate();
			}
			if (!filesPresent)
				usage();
		}
		
	}

	public static void usage() {
		System.err.println("Usage : java glemipsc.GLEMipsC [OPTIONS] <inputfiles>");
		System.err.println();
		System.err.println("Where OPTIONS is combination of the following parameters");
		System.err.println("\t-o file\t\tthe output file (default: output.mips)");
		System.err.println("\t-p     \t\tpre-parses only and prints the result");
		System.err.println("\t-s     \t\tsaves the object files");
		System.err.println("\t-asm   \t\toutputs an ASM source file instead executable file");
		System.err.println("\t-i32   \t\tuses 32bits implementation (default)");
		System.err.println("\t-emu   \t\tuses to run compiled program emulation");
		System.err.println("\t-I     \t\tuses to set an include library folder");
		System.err.println();
	}
}
